home *** CD-ROM | disk | FTP | other *** search
/ Collection of Tools & Utilities / Collection of Tools and Utilities.iso / edit / elv18src.zip / cut.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-01-02  |  15.1 KB  |  777 lines

  1. /* cut.c */
  2.  
  3. /* Author:
  4.  *    Steve Kirkendall
  5.  *    14407 SW Teal Blvd. #C
  6.  *    Beaverton, OR 97005
  7.  *    kirkenda@cs.pdx.edu
  8.  */
  9.  
  10.  
  11. /* This file contains function which manipulate the cut buffers. */
  12.  
  13. #include "config.h"
  14. #include "vi.h"
  15. #if TURBOC
  16. #include <process.h>        /* needed for getpid */
  17. #endif
  18. #if TOS
  19. #include <osbind.h>
  20. #define    rename(a,b)    Frename(0,a,b)
  21. #endif
  22.  
  23. # define NANONS    9    /* number of anonymous buffers */
  24.  
  25. static struct cutbuf
  26. {
  27.     short    *phys;    /* pointer to an array of #s of BLKs containing text */
  28.     int    nblks;    /* number of blocks in phys[] array */
  29.     int    start;    /* offset into first block of start of cut */
  30.     int    end;    /* offset into last block of end of cut */
  31.     int    tmpnum;    /* ID number of the temp file */
  32.     char    lnmode;    /* boolean: line-mode cut? (as opposed to char-mode) */
  33. }
  34.     named[27],    /* cut buffers "a through "z and ". */
  35.     anon[NANONS];    /* anonymous cut buffers */
  36.  
  37. static char    cbname;    /* name chosen for next cut/paste operation */
  38. static char    dotcb;    /* cut buffer to use if "doingdot" is set */
  39.  
  40.  
  41. #ifndef NO_RECYCLE
  42. /* This function builds a list of all blocks needed in the current tmp file
  43.  * for the contents of cut buffers.
  44.  * !!! WARNING: if you have more than ~450000 bytes of text in all of the
  45.  * cut buffers, then this will fail disastrously, because buffer overflow
  46.  * is *not* allowed for.
  47.  */
  48. int cutneeds(need)
  49.     BLK        *need;    /* this is where we deposit the list */
  50. {
  51.     struct cutbuf    *cb;    /* used to count through cut buffers */
  52.     int        i;    /* used to count through blocks of a cut buffer */
  53.     int        n;    /* total number of blocks in list */
  54.  
  55.     n = 0;
  56.  
  57.     /* first the named buffers... */
  58.     for (cb = named; cb < &named[27]; cb++)
  59.     {
  60.         if (cb->tmpnum != tmpnum)
  61.             continue;
  62.  
  63.         for (i = cb->nblks; i-- > 0; )
  64.         {
  65.             need->n[n++] = cb->phys[i];
  66.         }
  67.     }
  68.  
  69.     /* then the anonymous buffers */
  70.     for (cb = anon; cb < &anon[NANONS]; cb++)
  71.     {
  72.         if (cb->tmpnum != tmpnum)
  73.             continue;
  74.  
  75.         for (i = cb->nblks; i-- > 0; )
  76.         {
  77.             need->n[n++] = cb->phys[i];
  78.         }
  79.     }
  80.  
  81.     /* return the length of the list */
  82.     return n;
  83. }
  84. #endif
  85.  
  86. static void maybezap(num)
  87.     int    num;    /* the tmpnum of the temporary file to [maybe] delete */
  88. {
  89.     char    cutfname[80];
  90.     int    i;
  91.  
  92.     /* if this is the current tmp file, then we'd better keep it! */
  93.     if (tmpfd >= 0 && num == tmpnum)
  94.     {
  95.         return;
  96.     }
  97.  
  98.     /* see if anybody else needs this tmp file */
  99.     for (i = 27; --i >= 0; )
  100.     {
  101.         if (named[i].nblks > 0 && named[i].tmpnum == num)
  102.         {
  103.             break;
  104.         }
  105.     }
  106.     if (i < 0)
  107.     {
  108.         for (i = NANONS; --i >= 0 ; )
  109.         {
  110.             if (anon[i].nblks > 0 && anon[i].tmpnum == num)
  111.             {
  112.                 break;
  113.             }
  114.         }
  115.     }
  116.  
  117.     /* if nobody else needs it, then discard the tmp file */
  118.     if (i < 0)
  119.     {
  120. #if MSDOS || TOS || OS2
  121.         strcpy(cutfname, o_directory);
  122.         if ((i = strlen(cutfname)) && !strchr(":/\\", cutfname[i - 1]))
  123.             cutfname[i++] = SLASH;
  124.         sprintf(cutfname + i, TMPNAME + 3, getpid(), num);
  125. #else
  126.         sprintf(cutfname, TMPNAME, o_directory, getpid(), num);
  127. #endif
  128.         unlink(cutfname);
  129.     }
  130. }
  131.  
  132. /* This function frees a cut buffer.  If it was the last cut buffer that
  133.  * refered to an old temp file, then it will delete the temp file. */
  134. static void cutfree(buf)
  135.     struct cutbuf    *buf;
  136. {
  137.     int    num;
  138.  
  139.     /* return immediately if the buffer is already empty */
  140.     if (buf->nblks <= 0)
  141.     {
  142.         return;
  143.     }
  144.  
  145.     /* else free up stuff */
  146.     num = buf->tmpnum;
  147.     buf->nblks = 0;
  148. #ifdef DEBUG
  149.     if (!buf->phys)
  150.         msg("cutfree() tried to free a NULL buf->phys pointer.");
  151.     else
  152. #endif
  153.     _free_((char *)buf->phys);
  154.  
  155.     /* maybe delete the temp file */
  156.     maybezap(num);
  157. }
  158.  
  159. /* This function is called when we are about to abort a tmp file.
  160.  *
  161.  * To minimize the number of extra files lying around, only named cut buffers
  162.  * are preserved in a file switch; the anonymous buffers just go away.
  163.  */
  164. void cutswitch()
  165. {
  166.     int    i;
  167.  
  168.     /* mark the current temp file as being "obsolete", and close it.  */
  169.     storename((char *)0);
  170.     close(tmpfd);
  171.     tmpfd = -1;
  172.  
  173.     /* discard all anonymous cut buffers */
  174.     for (i = 0; i < NANONS; i++)
  175.     {
  176.         cutfree(&anon[i]);
  177.     }
  178.  
  179.     /* delete the temp file, if we don't really need it */
  180.     maybezap(tmpnum);
  181. }
  182.  
  183. /* This function should be called just before termination of vi */
  184. void cutend()
  185. {
  186.     int    i;
  187.  
  188.     /* free the anonymous buffers, if they aren't already free */
  189.     cutswitch();
  190.  
  191.     /* free all named cut buffers, since they might be forcing an older
  192.      * tmp file to be retained.
  193.      */
  194.     for (i = 0; i < 27; i++)
  195.     {
  196.         cutfree(&named[i]);
  197.     }
  198.  
  199.     /* delete the temp file */
  200.     maybezap(tmpnum);
  201. }
  202.  
  203.  
  204. /* This function is used to select the cut buffer to be used next */
  205. void cutname(name)
  206.     int    name;    /* a single character */
  207. {
  208.     cbname = name;
  209. }
  210.  
  211.  
  212. #ifndef NO_LEARN
  213. /* This function appends a single character to a cut buffer; it is used
  214.  * during "learn" mode to record a keystroke.  The buffer to use is determined
  215.  * by an external variable, `learnbuf'; this variable contains the buffer's
  216.  * name while learning, or '\0' if not learning.
  217.  */
  218. void learnkey(key)
  219.     char        key;    /* keystroke to append to learning buffer */
  220. {
  221.     static char    prevlearn;    /* previously learned buffer name */
  222.     static char    buf[BLKSIZE];    /* used for storing keystrokes */
  223.     static int    nkeys;        /* number of keystrokes in buf[] */
  224.     struct cutbuf    *cb;        /* ptr to buffer being saved */
  225.     long        seekpos;    /* where saved cutbuf's text goes */
  226.  
  227.     /* if we're ending a learn operation, then save keystokes in a cutbuf */
  228.     if (learn != prevlearn && prevlearn)
  229.     {
  230.         /* choose the cutbuffer to use; free its old contents, if any */
  231.         cb = &named[prevlearn - 'a'];
  232.         cutfree(cb);
  233.  
  234.         /* delete the final "]a" (or whatever) from the keystoke buffer */
  235.         nkeys -= 2;
  236.  
  237.         /* allocate a BLK for storage of the keystrokes */
  238.         cb->phys = (short *)malloc(sizeof(short));
  239.         cb->nblks = 1;
  240.         cb->start = 0;
  241.         cb->end = nkeys;
  242.         cb->tmpnum = tmpnum;
  243. #ifndef NO_RECYCLE
  244.         seekpos = allocate();
  245.         lseek(tmpfd, seekpos, 0);
  246. #else
  247.         seekpos = lseek(tmpfd, 0L, 2);
  248. #endif
  249.         cb->phys[0] = (short)(seekpos / BLKSIZE);
  250.  
  251.         /* write the keystokes there */
  252.         if (write(tmpfd, buf, (unsigned)BLKSIZE) != BLKSIZE)
  253.         {
  254.             msg("Trouble writing to tmp file");
  255.             deathtrap(0);
  256.         }
  257.  
  258.         /* saving complete */
  259.         prevlearn = '\0';
  260.         nkeys = 0;
  261.     }
  262.  
  263.     /* if we're learning a buffer now, save the keystroke */
  264.     if (learn)
  265.     {
  266.         prevlearn = learn;
  267.         buf[nkeys++] = key;
  268.         if (nkeys >= BLKSIZE - 2)
  269.         {
  270.             msg("Learn buffer full");
  271.             learn = 0;
  272.             nkeys += 2; /* <- to fake "]a" in keystroke buffer */
  273.         }
  274.     }
  275. }
  276. #endif /* !NO_LEARN */
  277.  
  278.  
  279.  
  280. /* This function copies a selected segment of text to a cut buffer */
  281. void cut(from, to)
  282.     MARK    from;        /* start of text to cut */
  283.     MARK    to;        /* end of text to cut */
  284. {
  285.     int        first;    /* logical number of first block in cut */
  286.     int        last;    /* logical number of last block used in cut */
  287.     long        line;    /* a line number */
  288.     int        lnmode;    /* boolean: will this be a line-mode cut? */
  289.     MARK        delthru;/* end of text temporarily inserted for apnd */
  290.     REG struct cutbuf *cb;
  291.     REG long    l;
  292.     REG int        i;
  293.     REG char    *scan;
  294.     char        *blkc;
  295.  
  296.     /* detect whether this must be a line-mode cut or char-mode cut */
  297.     if (markidx(from) == 0 && markidx(to) == 0)
  298.         lnmode = TRUE;
  299.     else
  300.         lnmode = FALSE;
  301.  
  302.     /* by default, we don't "delthru" anything */
  303.     delthru = MARK_UNSET;
  304.  
  305.     /* handle the "doingdot" quirks */
  306.     if (doingdot)
  307.     {
  308.         if (!cbname)
  309.         {
  310.             cbname = dotcb;
  311.         }
  312.     }
  313.     else if (cbname != '.')
  314.     {
  315.         dotcb = cbname;
  316.     }
  317.  
  318.     /* decide which cut buffer to use */
  319.     if (!cbname)
  320.     {
  321.         /* free up the last anonymous cut buffer */
  322.         cutfree(&anon[NANONS - 1]);
  323.  
  324.         /* shift the anonymous cut buffers */
  325.         for (i = NANONS - 1; i > 0; i--)
  326.         {
  327.             anon[i] = anon[i - 1];
  328.         }
  329.  
  330.         /* use the first anonymous cut buffer */
  331.         cb = anon;
  332.         cb->nblks = 0;
  333.     }
  334.     else if (cbname >= 'a' && cbname <= 'z')
  335.     {
  336.         cb = &named[cbname - 'a'];
  337.         cutfree(cb);
  338.     }
  339. #ifndef CRUNCH
  340.     else if (cbname >= 'A' && cbname <= 'Z')
  341.     {
  342.         cb = &named[cbname - 'A'];
  343.         if (cb->nblks > 0)
  344.         {
  345.             /* resolve linemode/charmode differences */
  346.             if (!lnmode && cb->lnmode)
  347.             {
  348.                 from &= ~(BLKSIZE - 1);
  349.                 if (markidx(to) != 0 || to == from)
  350.                 {
  351.                     to = to + BLKSIZE - markidx(to);
  352.                 }
  353.                 lnmode = TRUE;
  354.             }
  355.  
  356.             /* insert the old cut-buffer before the new text */
  357.             mark[28] = to;
  358.             delthru = paste(from, FALSE, TRUE);
  359.             if (delthru == MARK_UNSET)
  360.             {
  361.                 return;
  362.             }
  363.             delthru++;
  364.             to = mark[28];
  365.         }
  366.         cutfree(cb);
  367.     }
  368. #endif /* not CRUNCH */
  369.     else if (cbname == '.')
  370.     {
  371.         cb = &named[26];
  372.         cutfree(cb);
  373.     }
  374.     else
  375.     {
  376.         msg("Invalid cut buffer name: \"%c", cbname);
  377.         dotcb = cbname = '\0';
  378.         return;
  379.     }
  380.     cbname = '\0';
  381.     cb->tmpnum = tmpnum;
  382.  
  383.     /* detect whether we're doing a line mode cut */
  384.     cb->lnmode = lnmode;
  385.  
  386.     /* ---------- */
  387.  
  388.     /* Reporting... */    
  389.     if (markidx(from) == 0 && markidx(to) == 0)
  390.     {
  391.         rptlines = markline(to) - markline(from);
  392.         rptlabel = "yanked";
  393.     }
  394.  
  395.     /* ---------- */
  396.  
  397.     /* make sure each block has a physical disk address */
  398.     blksync();
  399.  
  400.     /* find the first block in the cut */
  401.     line = markline(from);
  402.     for (first = 1; line > lnum[first]; first++)
  403.     {
  404.     }
  405.  
  406.     /* fetch text of the block containing that line */
  407.     blkc = scan = blkget(first)->c;
  408.  
  409.     /* find the mark in the block */
  410.     for (l = lnum[first - 1]; ++l < line; )
  411.     {
  412.         while (*scan++ != '\n')
  413.         {
  414.         }
  415.     }
  416.     scan += markidx(from);
  417.  
  418.     /* remember the offset of the start */
  419.     cb->start = scan - blkc;
  420.  
  421.     /* ---------- */
  422.  
  423.     /* find the last block in the cut */
  424.     line = markline(to);
  425.     for (last = first; line > lnum[last]; last++)
  426.     {
  427.     }
  428.  
  429.     /* fetch text of the block containing that line */
  430.     if (last != first)
  431.     {
  432.         blkc = scan = blkget(last)->c;
  433.     }
  434.     else
  435.     {
  436.         scan = blkc;
  437.     }
  438.  
  439.     /* find the mark in the block */
  440.     for (l = lnum[last - 1]; ++l < line; )
  441.     {
  442.         while (*scan++ != '\n')
  443.         {
  444.         }
  445.     }
  446.     if (markline(to) <= nlines)
  447.     {
  448.         scan += markidx(to);
  449.     }
  450.  
  451.     /* remember the offset of the end */
  452.     cb->end = scan - blkc;
  453.  
  454.     /* ------- */
  455.  
  456.     /* remember the physical block numbers of all included blocks */
  457.     cb->nblks = last - first;
  458.     if (cb->end > 0)
  459.     {
  460.         cb->nblks++;
  461.     }
  462.     else /*!!!*/
  463.     {
  464.         cb->end = BLKSIZE;
  465.     }
  466. #ifdef lint
  467.     cb->phys = (short *)0;
  468. #else
  469.     cb->phys = (short *)malloc((unsigned)(cb->nblks * sizeof(short)));
  470. #endif
  471.     for (i = 0; i < cb->nblks; i++)
  472.     {
  473.         cb->phys[i] = hdr.n[first++];
  474.     }
  475.  
  476. #ifndef CRUNCH
  477.     /* if we temporarily inserted text for appending, then delete that
  478.      * text now -- before the user sees it.
  479.      */
  480.     if (delthru)
  481.     {
  482.         line = rptlines;
  483.         delete(from, delthru);
  484.         rptlines = line;
  485.         rptlabel = "yanked";
  486.     }
  487. #endif /* not CRUNCH */
  488. }
  489.  
  490.  
  491. static void readcutblk(cb, blkno)
  492.     struct cutbuf    *cb;
  493.     int        blkno;
  494. {
  495.     char        cutfname[50];/* name of an old temp file */
  496.     int        fd;    /* either tmpfd or the result of open() */
  497. #if MSDOS || TOS || OS2
  498.     int        i;
  499. #endif
  500.  
  501.     /* decide which fd to use */
  502.     if (cb->tmpnum == tmpnum)
  503.     {
  504.         fd = tmpfd;
  505.     }
  506.     else
  507.     {
  508. #if MSDOS || TOS || OS2
  509.         strcpy(cutfname, o_directory);
  510.         if ((i = strlen(cutfname)) && !strchr(":/\\", cutfname[i-1]))
  511.             cutfname[i++]=SLASH;
  512.         sprintf(cutfname+i, TMPNAME+3, getpid(), cb->tmpnum);
  513. #else
  514.         sprintf(cutfname, TMPNAME, o_directory, getpid(), cb->tmpnum);
  515. #endif
  516.         fd = open(cutfname, O_RDONLY);
  517.     }
  518.  
  519.     /* get the block */
  520.     lseek(fd, (long)cb->phys[blkno] * (long)BLKSIZE, 0);
  521.     if (read(fd, tmpblk.c, (unsigned)BLKSIZE) != BLKSIZE)
  522.     {
  523.         msg("Error reading back from tmp file for pasting!");
  524.     }
  525.  
  526.     /* close the fd, if it isn't tmpfd */
  527.     if (fd != tmpfd)
  528.     {
  529.         close(fd);
  530.     }
  531. }
  532.  
  533.  
  534. /* This function inserts text from a cut buffer, and returns the MARK where
  535.  * insertion ended.  Return MARK_UNSET on errors.
  536.  */
  537. MARK paste(at, after, retend)
  538.     MARK    at;    /* where to insert the text */
  539.     int    after;    /* boolean: insert after mark? (rather than before) */
  540.     int    retend;    /* boolean: return end of text? (rather than start) */
  541. {
  542.     REG struct cutbuf    *cb;
  543.     REG int            i;
  544.  
  545.     /* handle the "doingdot" quirks */
  546.     if (doingdot)
  547.     {
  548.         if (!cbname)
  549.         {
  550.             if (dotcb >= '1' && dotcb < '1' + NANONS - 1)
  551.             {
  552.                 dotcb++;
  553.             }
  554.             cbname = dotcb;
  555.         }
  556.     }
  557.     else if (cbname != '.')
  558.     {
  559.         dotcb = cbname;
  560.     }
  561.  
  562.     /* decide which cut buffer to use */
  563.     if (cbname >= 'A' && cbname <= 'Z')
  564.     {
  565.         cb = &named[cbname - 'A'];
  566.     }
  567.     else if (cbname >= 'a' && cbname <= 'z')
  568.     {
  569.         cb = &named[cbname - 'a'];
  570.     }
  571.     else if (cbname >= '1' && cbname <= '9')
  572.     {
  573.         cb = &anon[cbname - '1'];
  574.     }
  575.     else if (cbname == '.')
  576.     {
  577.         cb = &named[26];
  578.     }
  579.     else if (!cbname)
  580.     {
  581.         cb = anon;
  582.     }
  583.     else
  584.     {
  585.         msg("Invalid cut buffer name: \"%c", cbname);
  586.         cbname = '\0';
  587.         return MARK_UNSET;
  588.     }
  589.  
  590.     /* make sure it isn't empty */
  591.     if (cb->nblks == 0)
  592.     {
  593.         if (cbname)
  594.             msg("Cut buffer \"%c is empty", cbname);
  595.         else
  596.             msg("Cut buffer is empty");
  597.         cbname = '\0';
  598.         return MARK_UNSET;
  599.     }
  600.     cbname = '\0';
  601.  
  602.     /* adjust the insertion MARK for "after" and line-mode cuts */
  603.     if (cb->lnmode)
  604.     {
  605.         at &= ~(BLKSIZE - 1);
  606.         if (after)
  607.         {
  608.             at += BLKSIZE;
  609.         }
  610.     }
  611.     else if (after)
  612.     {
  613.         /* careful! if markidx(at) == 0 we might be pasting into an
  614.          * empty line -- so we can't blindly increment "at".
  615.          */
  616.         if (markidx(at) == 0)
  617.         {
  618.             pfetch(markline(at));
  619.             if (plen != 0)
  620.             {
  621.                 at++;
  622.             }
  623.         }
  624.         else
  625.         {
  626.             at++;
  627.         }
  628.     }
  629.  
  630.     /* put a copy of the "at" mark in the mark[] array, so it stays in
  631.      * sync with changes made via add().
  632.      */
  633.     mark[27] = at;
  634.  
  635.     /* simple one-block paste? */
  636.     if (cb->nblks == 1)
  637.     {
  638.         /* get the block */
  639.         readcutblk(cb, 0);
  640.  
  641.         /* isolate the text we need within it */
  642.         if (cb->end)
  643.         {
  644.             tmpblk.c[cb->end] = '\0';
  645.         }
  646.  
  647.         /* insert it */
  648.         ChangeText
  649.         {
  650.             add(at, &tmpblk.c[cb->start]);
  651.         }
  652.     }
  653.     else
  654.     {
  655.         /* multi-block paste */
  656.  
  657.         ChangeText
  658.         {
  659.             i = cb->nblks - 1;
  660.  
  661.             /* add text from the last block first */
  662.             if (cb->end > 0)
  663.             {
  664.                 readcutblk(cb, i);
  665.                 tmpblk.c[cb->end] = '\0';
  666.                 add(at, tmpblk.c);
  667.                 i--;
  668.             }
  669.  
  670.             /* add intervening blocks */
  671.             while (i > 0)
  672.             {
  673.                 readcutblk(cb, i);
  674.                 add(at, tmpblk.c);
  675.                 i--;
  676.             }
  677.  
  678.             /* add text from the first cut block */
  679.             readcutblk(cb, 0);
  680.             add(at, &tmpblk.c[cb->start]);
  681.         }
  682.     }
  683.  
  684.     /* Reporting... */
  685.     rptlines = markline(mark[27]) - markline(at);
  686.     rptlabel = "pasted";
  687.  
  688.     /* return the mark at the beginning/end of inserted text */
  689.     if (retend)
  690.     {
  691.         return mark[27] - 1L;
  692.     }
  693.     return at;
  694. }
  695.  
  696.  
  697.  
  698.  
  699. #ifndef NO_AT
  700.  
  701. /* This function copies characters from a cut buffer into a string.
  702.  * It returns the number of characters in the cut buffer.  If the cut
  703.  * buffer is too large to fit in the string (i.e. if cb2str() returns
  704.  * a number >= size) then the characters will not have been copied.
  705.  * It returns 0 if the cut buffer is empty, and -1 for invalid cut buffers.
  706.  */
  707. int cb2str(name, buf, size)
  708.     int    name;    /* the name of a cut-buffer to get: a-z only! */
  709.     char    *buf;    /* where to put the string */
  710.     unsigned size;    /* size of buf */
  711. {
  712.     REG struct cutbuf    *cb;
  713.     REG char        *src;
  714.     REG char        *dest;
  715.  
  716.     /* decide which cut buffer to use */
  717.     if (name >= 'a' && name <= 'z')
  718.     {
  719.         cb = &named[name - 'a'];
  720.     }
  721. #if 1 /* [sdw] allow selection of anonymous buffer */
  722.     else if (!name)
  723.     {
  724.         cb = anon;
  725.     }
  726. #endif
  727.     else
  728.     {
  729.         return -1;
  730.     }
  731.  
  732.     /* if the buffer is empty, return 0 */
  733.     if (cb->nblks == 0)
  734.     {
  735.         return 0;
  736.     }
  737.  
  738.     /* !!! if not a single-block cut, then fail */
  739.     if (cb->nblks != 1)
  740.     {
  741.         return size;
  742.     }
  743.  
  744.     /* if too big, return the size now, without doing anything */
  745.     if ((unsigned)(cb->end - cb->start) >= size)
  746.     {
  747.         return cb->end - cb->start;
  748.     }
  749.  
  750.     /* get the block */
  751.     readcutblk(cb, 0);
  752.  
  753.     /* isolate the string within that blk */
  754.     if (cb->start == 0)
  755.     {
  756.         tmpblk.c[cb->end] = '\0';
  757.     }
  758.     else
  759.     {
  760.         for (dest = tmpblk.c, src = dest + cb->start; src < tmpblk.c + cb->end; )
  761.         {
  762.             *dest++ = *src++;
  763.         }
  764.         *dest = '\0';
  765.     }
  766.  
  767.     /* copy the string into the buffer */
  768.     if (buf != tmpblk.c)
  769.     {
  770.         strcpy(buf, tmpblk.c);
  771.     }
  772.  
  773.     /* return the length */
  774.     return cb->end - cb->start;
  775. }
  776. #endif
  777.